package com.cn.ycy.filter;


import com.cn.ycy.conf.FilterOrder;
import com.cn.ycy.filter.entity.CachedBodyOutputMessage;
import com.cn.ycy.filter.entity.Config;
import com.cn.ycy.filter.entity.DefaultServerRequest;
import com.cn.ycy.util.GsonUtil;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;

/**
 * 微信回调
 * 1.首次验证url 微信提供echostr参数
 * 2.在微信"安全模式"下为微信请求体验签，对回复微信的响应体加签（微信"明文模式"下不需要验签和加签，请求体和响应体本身也是明文）
 * 3.在微信"安全模式"下为微信请求体解密，对回复微信的响应体加密
 */
@Component
public class AddSignatureAndEncryptDencryptForWechatGatewayFilterFactory extends AbstractGatewayFilterFactory<Config> {

  private final static Logger LOGGER = LoggerFactory.getLogger(AddSignatureAndEncryptDencryptForWechatGatewayFilterFactory.class);

  public static final String apiId = "apiId";
  public static final String pluginParam = "pluginParam";

  public AddSignatureAndEncryptDencryptForWechatGatewayFilterFactory() {
    super(Config.class);
  }

  @Override
  public GatewayFilter apply(Config config) {
    final PluginParamEntity pluginParamEntity = GsonUtil.GsonToBean(config.getPluginParam(), PluginParamEntity.class);
    return new ModifyRequestResponseBodyGatewayFilter(config, pluginParamEntity);
  }

  @Override
  public List<String> shortcutFieldOrder() {
    return Arrays.asList(apiId, pluginParam);
  }

  private class ModifyRequestResponseBodyGatewayFilter implements GatewayFilter, Ordered {

    private final Config config;

    private final PluginParamEntity pluginParamEntity;

    private final List<HttpMessageReader<?>> messageReaders;

    ModifyRequestResponseBodyGatewayFilter(Config config, PluginParamEntity pluginParamEntity) {
      this.config = config;
      this.pluginParamEntity = pluginParamEntity;
      this.messageReaders = HandlerStrategies.withDefaults().messageReaders();
    }

    @SuppressWarnings("unchecked")
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

      // mediaType
      MediaType mediaType = exchange.getRequest().getHeaders().getContentType();

//      MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
      //读取参数
//      final String signature = queryParams.getFirst("signature");
//      final String timestamp = queryParams.getFirst("timestamp");
//      final String nonce = queryParams.getFirst("nonce");
//      final String echostr = queryParams.getFirst("echostr");
//      //微信"安全模式"会返回这两个参数
//      final String msg_signature = queryParams.getFirst("msg_signature");
//      final String encrypt_type = queryParams.getFirst("encrypt_type");
//      final boolean isAes = "aes".equals(encrypt_type);
//      final WXBizMsgCrypt wxBizMsgCrypt;

//      if (isAes) {
//        try {
//          wxBizMsgCrypt = new WXBizMsgCrypt(pluginParamEntity.getToken(), pluginParamEntity.getEncodingAESKey(), pluginParamEntity.getAppid());
//        } catch (AesException e) {
//          return Mono.error(e);
//        }
//      } else {
//        wxBizMsgCrypt = null;
//      }
//
//      //首次请求验证url,网关直接响应echostr
//      if (echostr != null) {
//        if (SHA1.getSHA1(pluginParamEntity.getToken(), timestamp, nonce, StringUtils.EMPTY).equals(signature)) {
//          return firstCheck(exchange, echostr);
//        } else {
//          return Mono.error(new BusinessException(BusinessErrorCodeEnums.CALLBACK_CHECK));
//        }
//      }

//      //微信 明文模式
//      if (!isAes) {
//        return chain.filter(exchange);
//      }
      //微信 安全模式负责:验签、加签、解密请求报文、加密响应报文
      ServerRequest serverRequest = new DefaultServerRequest(exchange, this.messageReaders);

      Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(requestBody -> {
        LOGGER.info("modifiedBody {}", requestBody);
//        //验签和解密 请求报文
//        String xmlData_encrypt;
//        try {
//          xmlData_encrypt = wxBizMsgCrypt.decryptMsg(msg_signature, timestamp, nonce, requestBody);
//        } catch (AesException e) {
//          BusinessErrorCodeEnums callbackCheck = BusinessErrorCodeEnums.CALLBACK_CHECK;
//          LOGGER.warn("error {},because of {}", callbackCheck.getDesc(), e.getMessage());
//          return Mono.error(new BusinessException(callbackCheck));
//        }
        if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
          // 对原先的body进行修改操作
          String newBody = "{\"testName\":\"testValue\"}";
          return Mono.just(newBody);
        }
        return Mono.empty();
      });

      BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
      HttpHeaders headers = new HttpHeaders();
      headers.putAll(exchange.getRequest().getHeaders());

      // the new content type will be computed by bodyInserter
      // and then set in the request decorator
      headers.remove(HttpHeaders.CONTENT_LENGTH);

      CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
      return bodyInserter.insert(outputMessage, new BodyInserterContext())
              // .log("modify_request", Level.INFO)
              .then(Mono.defer(() -> {
                return chain.filter(
                        exchange.mutate()
                                .request(decorate(exchange, headers, outputMessage))
                                .response(decorate(exchange))
                                .build()
                );
              }));
    }

    /**
     * 直接响应客户端 message
     */
    private Mono<Void> firstCheck(ServerWebExchange exchange, String message) {

      return Mono.defer(() -> {
        byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
        ServerHttpResponse response = exchange.getResponse();
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        return response.writeWith(Flux.just(buffer));
      });
    }

    private ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
      return new ServerHttpRequestDecorator(exchange.getRequest()) {
        @Override
        public HttpHeaders getHeaders() {
          long contentLength = headers.getContentLength();
          HttpHeaders httpHeaders = new HttpHeaders();
          httpHeaders.putAll(super.getHeaders());
          if (contentLength > 0) {
            httpHeaders.setContentLength(contentLength);
          } else {
            // TODO: this causes a 'HTTP/1.1 411 Length Required' // on
            // httpbin.org
            httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
          }
          return httpHeaders;
        }

        @Override
        public Flux<DataBuffer> getBody() {
          return outputMessage.getBody();
        }
      };
    }

    @SuppressWarnings("unchecked")
    private ServerHttpResponseDecorator decorate(ServerWebExchange exchange) {
      return new ServerHttpResponseDecorator(exchange.getResponse()) {

        @Override
        public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
          String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
          HttpHeaders httpHeaders = new HttpHeaders();
          // explicitly add it in this way instead of
          // 'httpHeaders.setContentType(originalResponseContentType)'
          // this will prevent exception in case of using non-standard media
          // types like "Content-Type: image"
          httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);

          ClientResponse clientResponse = ClientResponse
                  .create(exchange.getResponse().getStatusCode())
                  .headers(headers -> headers.putAll(httpHeaders))
                  .body(Flux.from(body)).build();

          // TODO: flux or mono
          Mono modifiedBody = clientResponse.bodyToMono(String.class)
                  .flatMap(originalBody -> {
                    //加签和并对响应明文加密
//                    String outputStr = bizMsgCrypt.encryptMsg(originalBody, System.currentTimeMillis() + "", UUID.randomUUID().toString());
                    String outputStr = "{\"new\":1}";
                    return Mono.just(outputStr);
                  });

          BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,
                  String.class);
          CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(
                  exchange, exchange.getResponse().getHeaders());
          return bodyInserter.insert(outputMessage, new BodyInserterContext())
                  .then(Mono.defer(() -> {
                    Flux<DataBuffer> messageBody = outputMessage.getBody();
                    HttpHeaders headers = getDelegate().getHeaders();
                    if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
                      messageBody = messageBody.doOnNext(data -> headers
                              .setContentLength(data.readableByteCount()));
                    }
                    // TODO: fail if isStreamingMediaType?
                    return getDelegate().writeWith(messageBody);
                  }));
        }

        @Override
        public Mono<Void> writeAndFlushWith(
                Publisher<? extends Publisher<? extends DataBuffer>> body) {
          return writeWith(Flux.from(body).flatMapSequential(p -> p));
        }
      };
    }

    @Override
    public int getOrder() {
      return FilterOrder.RESPONSE_ORDER - 1;
    }

    @Override
    public String toString() {
      return filterToStringCreator(AddSignatureAndEncryptDencryptForWechatGatewayFilterFactory.this)
              .append(config.getApiId(), config.getPluginParam()).toString();
    }

  }

  class PluginParamEntity {

    public String appid;
    public String EncodingAESKey;
    public String token;

    public String getAppid() {
      return appid;
    }

    public void setAppid(String appid) {
      this.appid = appid;
    }

    public String getEncodingAESKey() {
      return EncodingAESKey;
    }

    public void setEncodingAESKey(String encodingAESKey) {
      EncodingAESKey = encodingAESKey;
    }

    public String getToken() {
      return token;
    }

    public void setToken(String token) {
      this.token = token;
    }
  }


}
